﻿Imports System.Drawing
Imports System.IO
''' <summary>
''' 这个类是撤销和重做的关键类
''' </summary>
''' <remarks></remarks>
Public Class StepManager
    '重做撤销可能改变
    Public Event UnReDoChanged()
    ''' <summary>
    ''' 撤销队列
    ''' </summary>
    ''' <remarks></remarks>
    Private UnDoStepList As New Stack(Of StruStep)
    ''' <summary>
    ''' 重做队列
    ''' </summary>
    ''' <remarks></remarks>
    Private ReDoStepList As New Stack(Of StruStep)
    ''' <summary>
    ''' 步骤名称
    ''' </summary>
    ''' <remarks></remarks>
    Enum StepName
        ''' <summary>
        ''' 无操作
        ''' </summary>
        ''' <remarks></remarks>
        None
        ''' <summary>
        ''' 插入字符
        ''' </summary>
        ''' <remarks></remarks>
        InsertChar
        ''' <summary>
        ''' 插入字符串
        ''' </summary>
        ''' <remarks></remarks>
        InsertSelection
        ''' <summary>
        ''' 插入换行
        ''' </summary>
        ''' <remarks></remarks>
        InsertEnter
        ''' <summary>
        ''' 合并行
        ''' </summary>
        ''' <remarks></remarks>
        MegerLine
        ''' <summary>
        ''' 更新字符
        ''' </summary>
        ''' <remarks></remarks>
        UpdateChar
        ''' <summary>
        ''' 更新字符串
        ''' </summary>
        ''' <remarks></remarks>
        UpdateSection
        ''' <summary>
        ''' 使用BackSpace键删除字符
        ''' </summary>
        ''' <remarks></remarks>
        DeleteCharByBS
        ''' <summary>
        ''' 使用Del键删除字符
        ''' </summary>
        ''' <remarks></remarks>
        DeleteCharByDel
        ''' <summary>
        ''' 删除选定文本
        ''' </summary>
        ''' <remarks></remarks>
        DeleteSelection
        ''' <summary>
        ''' 复合操作开始
        ''' </summary>
        ''' <remarks></remarks>
        MulitStart
        ''' <summary>
        ''' 列操作：插入字符
        ''' </summary>
        ''' <remarks></remarks>
        ColumnInsertChar
        ''' <summary>
        ''' 列操作：删除字符(使用DEL键)
        ''' </summary>
        ''' <remarks></remarks>
        ColumnDelCharByDel
        ''' <summary>
        ''' 列操作：删除字符(使用BackSpace键)
        ''' </summary>
        ''' <remarks></remarks>
        ColumnDelCharByBS
        ''' <summary>
        ''' 复合操作：移动字符
        ''' </summary>
        ''' <remarks></remarks>
        MoveSelection
        ''' <summary>
        ''' 箱型选择：删除选中部分
        ''' </summary>
        ''' <remarks></remarks>
        BoxDelete
        ''' <summary>
        ''' 复合操作结束
        ''' </summary>
        ''' <remarks></remarks>
        MulitEnd
    End Enum
    ''' <summary>
    ''' 可以撤销的命令的长度
    ''' </summary>
    ''' <remarks></remarks>
    ReadOnly Property UndoStepLength() As Integer
        Get
            Return UnDoStepList.Count
        End Get
    End Property
    ''' <summary>
    ''' 可以重做的命令的长度
    ''' </summary>
    ''' <remarks></remarks>
    ReadOnly Property RedoStepLength() As Integer
        Get
            Return ReDoStepList.Count
        End Get
    End Property
    ''' <summary>
    ''' 撤销
    ''' </summary>
    ''' <remarks></remarks>
    Public Sub Undo()
        Dim PreStack As StruStep
        If UnDoStepList.Count > 0 Then
            PreStack = UnDoStepList.Peek
            If PreStack.Action = StepName.MulitEnd Then
                '复合操作
                'MultiEnd
                UnDoStepList.Pop()
                ReDoStepList.Push(PreStack)
                PreStack = UnDoStepList.Pop
                Do Until PreStack.Action = StepName.MulitStart
                    ReserveOperation(PreStack)
                    ReDoStepList.Push(PreStack)
                    PreStack = UnDoStepList.Pop
                Loop
                'MulitStart 
                ReDoStepList.Push(PreStack)
            Else
                ReserveOperation(PreStack)
                UnDoStepList.Pop()
                ReDoStepList.Push(PreStack)
            End If
        End If
    End Sub
    ''' <summary>
    ''' 重做
    ''' </summary>
    ''' <remarks></remarks>
    Public Sub Redo()
        Dim PreStack As StruStep
        If ReDoStepList.Count > 0 Then
            PreStack = ReDoStepList.Peek
            If PreStack.Action = StepName.MulitStart Then
                '复合操作
                'MulitStart 
                ReDoStepList.Pop()
                UnDoStepList.Push(PreStack)
                PreStack = ReDoStepList.Pop()
                Do Until PreStack.Action = StepName.MulitEnd
                    DoOperation(PreStack)
                    UnDoStepList.Push(PreStack)
                    PreStack = ReDoStepList.Pop
                Loop
                'MultiEnd
                UnDoStepList.Push(PreStack)
            Else
                DoOperation(PreStack)
                ReDoStepList.Pop()
                UnDoStepList.Push(PreStack)
            End If
        End If
    End Sub
    ''' <summary>
    ''' 核心：逆操作
    ''' </summary>
    ''' <remarks></remarks>
    Private Sub ReserveOperation(ByVal mStep As StruStep)
        Select Case mStep.Action
            Case StepName.InsertChar
                '插入字符的逆操作，删除字符
                '选中字符
                Dim selEnd As Point = New Point(mStep.Pos.X + mStep.Text.Length, mStep.Pos.Y)
                ExDocument.UpdateSelection(mStep.Pos, selEnd)
                ExDocument.ExRealLineManager.SelectText(mStep.Pos.Y, mStep.Pos.X, mStep.Text.Length)
                ExDocument.InputPosition = mStep.Pos
                '删除选中部分
                ExDocument.DeleteSelection(False)
            Case StepName.InsertSelection, StepName.ColumnInsertChar
                '插入字符的逆操作，删除字符
                Dim strArray() As String = Split(mStep.Text, vbCrLf)
                Dim selEnd As Point
                If strArray.Length = 1 Then
                    selEnd = New Point(mStep.Pos.X + strArray(0).Length, mStep.Pos.Y)
                Else
                    selEnd = New Point(strArray(strArray.Length - 1).Length + 1, mStep.Pos.Y + strArray.Length - 1)
                End If
                ExDocument.UpdateSelection(mStep.Pos, selEnd)
                ExDocument.InputPosition = New Point(strArray(strArray.Length - 1).Length + 1, mStep.Pos.Y + strArray.Length - 1)
                '删除选中部分
                ExDocument.DeleteSelection(False)
                If mStep.Action = StepName.ColumnInsertChar Then
                    '列模式标尺的再设定
                    ExDocument.ColumnModeList(mStep.Pos.Y) = mStep.Pos.X
                End If
            Case StepName.DeleteCharByDel, StepName.DeleteSelection, StepName.DeleteCharByBS, StepName.BoxDelete
                '删除字符的逆操作，插入字符
                ExDocument.InputPosition = mStep.Pos
                ExDocument.InsertSelection(mStep.Text, False)
                If mStep.Action = StepName.DeleteCharByDel Then
                    '如果是使用Del来删除的话，还需要调整插入点位置
                    ExDocument.InputPosition = New Point(ExDocument.InputPosition.X - mStep.Text.Length, ExDocument.InputPosition.Y)
                End If
            Case StepName.ColumnDelCharByBS, StepName.ColumnDelCharByDel
                ExDocument.InputPosition = mStep.Pos
                ExDocument.InsertSelection(mStep.Text, False)
                If mStep.Action = StepName.ColumnDelCharByBS Then
                    '如果是使用Del来删除的话，还需要调整插入点位置
                    ExDocument.ColumnModeList(mStep.Pos.Y) = mStep.Pos.X + mStep.Text.Length
                End If
            Case StepName.InsertEnter
                ExDocument.InputPosition = mStep.Pos
                '尽量不要直接调用原子操作
                ExDocument.atomic_MergeLine(mStep.Pos.Y)
            Case StepName.MegerLine
                ExDocument.InputPosition = mStep.Pos
                ExDocument.InsertEnter(False)
            Case StepName.UpdateChar, StepName.UpdateSection
                '插入字符的逆操作，删除字符
                Dim strArray() As String = Split(mStep.NewText, vbCrLf)
                Dim selEnd As Point
                If strArray.Length = 1 Then
                    selEnd = New Point(mStep.Pos.X + strArray(0).Length, mStep.Pos.Y)
                Else
                    selEnd = New Point(strArray(strArray.Length - 1).Length + 1, mStep.Pos.Y + strArray.Length - 1)
                End If
                ExDocument.UpdateSelection(mStep.Pos, selEnd)
                ExDocument.InputPosition = New Point(strArray(strArray.Length - 1).Length + 1, mStep.Pos.Y + strArray.Length - 1)
                '删除选中部分
                ExDocument.DeleteSelection(False)
                '删除字符的逆操作，插入字符
                ExDocument.InputPosition = mStep.Pos
                ExDocument.InsertSelection(mStep.Text, False)
        End Select
    End Sub
    ''' <summary>
    ''' 核心：正操作
    ''' </summary>
    ''' <remarks></remarks>
    Private Sub DoOperation(ByVal mStep As StruStep)
        ExDocument.InputPosition = mStep.Pos
        Select Case mStep.Action
            Case StepName.InsertChar, StepName.InsertSelection, StepName.ColumnInsertChar
                '插入字符串
                ExDocument.InsertSelection(mStep.Text, False)
                If mStep.Action = StepName.ColumnInsertChar Then
                    '列模式标尺的再设定
                    ExDocument.ColumnModeList(mStep.Pos.Y) = mStep.Pos.X + mStep.Text.Length
                End If
            Case StepName.DeleteCharByDel, StepName.DeleteSelection, StepName.DeleteCharByBS, StepName.BoxDelete, _
                 StepName.ColumnDelCharByBS, StepName.ColumnDelCharByDel
                Dim strArray() As String = Split(mStep.Text, vbCrLf)
                Dim selEnd As Point
                If strArray.Length = 1 Then
                    selEnd = New Point(mStep.Pos.X + strArray(0).Length, mStep.Pos.Y)
                Else
                    selEnd = New Point(strArray(strArray.Length - 1).Length + 1, mStep.Pos.Y + strArray.Length - 1)
                End If
                ExDocument.UpdateSelection(mStep.Pos, selEnd)
                ExDocument.InputPosition = New Point(strArray(strArray.Length - 1).Length + 1, mStep.Pos.Y + strArray.Length - 1)
                '删除选中部分
                ExDocument.DeleteSelection(False)
                If mStep.Action = StepName.ColumnDelCharByBS Then
                    '列模式标尺的再设定
                    ExDocument.ColumnModeList(mStep.Pos.Y) = mStep.Pos.X
                End If
            Case StepName.InsertEnter
                ExDocument.InsertEnter(False)
            Case StepName.MegerLine
                ExDocument.atomic_MergeLine(mStep.Pos.Y)
            Case StepName.UpdateChar, StepName.UpdateSection
                Dim strArray() As String = Split(mStep.Text, vbCrLf)
                Dim selEnd As Point
                If strArray.Length = 1 Then
                    selEnd = New Point(mStep.Pos.X + strArray(0).Length, mStep.Pos.Y)
                Else
                    selEnd = New Point(strArray(strArray.Length - 1).Length + 1, mStep.Pos.Y + strArray.Length - 1)
                End If
                ExDocument.UpdateSelection(mStep.Pos, selEnd)
                ExDocument.InputPosition = New Point(strArray(strArray.Length - 1).Length + 1, mStep.Pos.Y + strArray.Length - 1)
                '删除选中部分
                ExDocument.DeleteSelection(False)
                '删除字符的逆操作，插入字符
                ExDocument.InputPosition = mStep.Pos
                ExDocument.InsertSelection(mStep.NewText, False)
        End Select
    End Sub
    ''' <summary>
    ''' 新增动作
    ''' </summary>
    ''' <remarks></remarks>
    Public Sub Add(ByVal NewStep As StruStep)
        Dim IsRaiseEvent As Boolean
        If UnDoStepList.Count = 0 Then IsRaiseEvent = True
        '有任何新步骤的时候，都必须清除重做列表
        ReDoStepList.Clear()
        '前一个步骤的操作
        Dim PreStack As StruStep
        PreStack.Text = ""
        PreStack.NewText = ""
        If UnDoStepList.Count > 0 Then
            PreStack = UnDoStepList.Peek
        End If
        Select Case NewStep.Action
            Case StepName.InsertChar
                '插入字符：需要看看能不能合并
                If PreStack.Action = StepName.InsertChar Then
                    If (PreStack.Pos.X + PreStack.Text.Length = NewStep.Pos.X) And (PreStack.Pos.Y = NewStep.Pos.Y) Then
                        PreStack.Text = PreStack.Text + NewStep.Text
                        UnDoStepList.Pop()
                        UnDoStepList.Push(PreStack)
                        Exit Sub
                    End If
                End If
            Case StepName.UpdateChar
                '更新字符：需要看看能不能合并
                If PreStack.Action = StepName.UpdateChar Then
                    If (PreStack.Pos.X + PreStack.Text.Length = NewStep.Pos.X) And (PreStack.Pos.Y = NewStep.Pos.Y) Then
                        PreStack.Text = PreStack.Text + NewStep.Text
                        PreStack.NewText = PreStack.NewText + NewStep.NewText
                        UnDoStepList.Pop()
                        UnDoStepList.Push(PreStack)
                        Exit Sub
                    End If
                End If
            Case StepName.DeleteCharByDel
                '删除字符：需要看看能不能合并
                If PreStack.Action = StepName.DeleteCharByDel Then
                    '这种情况是Delete键引发的删除
                    If ComparePoint(PreStack.Pos, NewStep.Pos) = CompareResult.Equal Then
                        PreStack.Text = PreStack.Text + NewStep.Text
                        UnDoStepList.Pop()
                        UnDoStepList.Push(PreStack)
                        Exit Sub
                    End If
                End If
            Case StepName.DeleteCharByBS
                '删除字符：需要看看能不能合并
                If PreStack.Action = StepName.DeleteCharByBS Then
                    If (PreStack.Pos.X - NewStep.Text.Length = NewStep.Pos.X) And (PreStack.Pos.Y = NewStep.Pos.Y) Then
                        '这种情况是BackSpace键引发的删除
                        PreStack.Pos = NewStep.Pos
                        PreStack.Text = NewStep.Text + PreStack.Text
                        UnDoStepList.Pop()
                        UnDoStepList.Push(PreStack)
                        Exit Sub
                    End If
                End If
        End Select
        UnDoStepList.Push(NewStep)
        If IsRaiseEvent Then RaiseEvent UnReDoChanged()
    End Sub
    ''' <summary>
    ''' 新增动作[列操作]
    ''' </summary>
    ''' <remarks></remarks>
    Public Sub Add(ByVal NewStep As StruStep(), ByVal ActionName As StepName, ByVal FirstLine As Integer, ByVal LastLine As Integer)
        Dim IsRaiseEvent As Boolean
        If UnDoStepList.Count = 0 Then IsRaiseEvent = True
        '有任何新步骤的时候，都必须清除重做列表
        ReDoStepList.Clear()
        Dim PreStack As StruStep
        Dim Range As String
        Range = FirstLine & "/" & LastLine
        '前一个步骤的操作
        PreStack.Text = ""
        PreStack.NewText = ""
        If UnDoStepList.Count > 0 Then
            PreStack = UnDoStepList.Peek
        End If
        If (PreStack.Action = StepName.MulitEnd) And _
           (PreStack.Text = ActionName.ToString) And _
           (PreStack.NewText = Range) Then
            '如果上一个动作是复合操作，并且操作名称和这个操作名称一致,范围也一样的话
            Dim mStartLine As Integer = CInt(PreStack.NewText.Split("/")(0))
            Dim mEndLine As Integer = CInt(PreStack.NewText.Split("/")(1))
            Dim StepLength As Integer = mEndLine - mStartLine + 1
            Select Case ActionName
                Case StepName.ColumnInsertChar
                    '需要检查是不是可以合并
                    Dim CanMerge As Boolean = True  '是不是可以合并的标志
                    Dim OldSteps As StruStep() '旧的堆栈的长度为操作范围 + 2[首末标志]
                    ReDim OldSteps(StepLength + 1)
                    '尾部标志
                    OldSteps(StepLength) = UnDoStepList.Pop
                    '保持两个数组的顺序一致
                    For i As Integer = StepLength - 1 To 0 Step -1
                        OldSteps(i) = UnDoStepList.Pop
                    Next
                    '首部标志
                    OldSteps(StepLength + 1) = UnDoStepList.Pop
                    '开始判断
                    For i As Integer = 0 To StepLength - 1
                        If Not ((OldSteps(i).Pos.X + OldSteps(i).Text.Length = NewStep(i).Pos.X) And _
                           (OldSteps(i).Pos.Y = NewStep(i).Pos.Y)) Then
                            CanMerge = False
                            Exit For
                        Else
                            If OldSteps(i).Action <> ActionName Then
                                CanMerge = False
                                Exit For
                            End If
                        End If
                    Next
                    If CanMerge Then
                        '如果能合并，将所有合并后的操作放回堆栈中
                        UnDoStepList.Push(OldSteps(StepLength + 1))
                        For i As Integer = 0 To StepLength - 1
                            OldSteps(i).Text += NewStep(i).Text
                            UnDoStepList.Push(OldSteps(i))
                        Next
                        UnDoStepList.Push(OldSteps(StepLength))
                        Exit Sub
                    End If
                    '如果不能合并，将所有旧操作放回堆栈中
                    UnDoStepList.Push(OldSteps(StepLength))
                    For i As Integer = 0 To StepLength - 1
                        UnDoStepList.Push(OldSteps(i))
                    Next
                    UnDoStepList.Push(OldSteps(StepLength + 1))
                Case StepName.ColumnDelCharByDel
                    '这种情况是Del键引发的删除,需要检查是不是可以合并
                    Dim CanMerge As Boolean = True  '是不是可以合并的标志
                    Dim OldSteps As StruStep() '旧的堆栈的长度为操作范围 + 2[首末标志]
                    ReDim OldSteps(StepLength + 1)
                    '尾部标志
                    OldSteps(StepLength) = UnDoStepList.Pop
                    '保持两个数组的顺序一致
                    For i As Integer = StepLength - 1 To 0 Step -1
                        OldSteps(i) = UnDoStepList.Pop
                    Next
                    '首部标志
                    OldSteps(StepLength + 1) = UnDoStepList.Pop
                    '开始判断
                    If ComparePoint(OldSteps(0).Pos, NewStep(0).Pos) = CompareResult.Equal Then
                        For i As Integer = 0 To StepLength - 1
                            If Not (ComparePoint(OldSteps(i).Pos, NewStep(i).Pos) = CompareResult.Equal) Then
                                CanMerge = False
                                Exit For
                            Else
                                If OldSteps(i).Action <> StepName.ColumnDelCharByDel Then
                                    CanMerge = False
                                    Exit For
                                End If
                            End If
                        Next
                    End If
                    If CanMerge Then
                        '如果能合并，将所有合并后的操作放回堆栈中
                        UnDoStepList.Push(OldSteps(StepLength + 1))
                        For i As Integer = 0 To StepLength - 1
                            OldSteps(i).Text += NewStep(i).Text
                            UnDoStepList.Push(OldSteps(i))
                        Next
                        UnDoStepList.Push(OldSteps(StepLength))
                        Exit Sub
                    End If
                    '如果不能合并，将所有旧操作放回堆栈中
                    UnDoStepList.Push(OldSteps(StepLength))
                    For i As Integer = 0 To StepLength - 1
                        UnDoStepList.Push(OldSteps(i))
                    Next
                    UnDoStepList.Push(OldSteps(StepLength + 1))
                Case StepName.ColumnDelCharByBS
                    '这种情况是BackSpace键引发的删除,需要检查是不是可以合并
                    Dim CanMerge As Boolean = True  '是不是可以合并的标志
                    Dim OldSteps As StruStep() '旧的堆栈的长度为操作范围 + 2[首末标志]
                    ReDim OldSteps(StepLength + 1)
                    '尾部标志
                    OldSteps(StepLength) = UnDoStepList.Pop
                    '保持两个数组的顺序一致
                    For i As Integer = StepLength - 1 To 0 Step -1
                        OldSteps(i) = UnDoStepList.Pop
                    Next
                    '首部标志
                    OldSteps(StepLength + 1) = UnDoStepList.Pop
                    '开始判断
                    For i As Integer = 0 To StepLength - 1
                        If Not ((OldSteps(i).Pos.X - NewStep(i).Text.Length = NewStep(i).Pos.X) And _
                           (OldSteps(i).Pos.Y = NewStep(i).Pos.Y)) Then
                            CanMerge = False
                            Exit For
                        Else
                            If OldSteps(i).Action <> StepName.ColumnDelCharByBS Then
                                CanMerge = False
                                Exit For
                            End If
                        End If
                    Next
                    If CanMerge Then
                        '如果能合并，将所有合并后的操作放回堆栈中
                        UnDoStepList.Push(OldSteps(StepLength + 1))
                        For i As Integer = 0 To StepLength - 1
                            OldSteps(i).Pos = NewStep(i).Pos
                            OldSteps(i).Text = NewStep(i).Text + OldSteps(i).Text
                            UnDoStepList.Push(OldSteps(i))
                        Next
                        UnDoStepList.Push(OldSteps(StepLength))
                        Exit Sub
                    End If
                    '如果不能合并，将所有旧操作放回堆栈中
                    UnDoStepList.Push(OldSteps(StepLength))
                    For i As Integer = 0 To StepLength - 1
                        UnDoStepList.Push(OldSteps(i))
                    Next
                    UnDoStepList.Push(OldSteps(StepLength + 1))
                Case Else
            End Select
        End If
        '直接将操作信息放入列表中
        Dim T1 As StruStep
        With T1
            .Action = StepName.MulitStart
            .Text = ActionName.ToString
            .NewText = Range
        End With
        Me.UnDoStepList.Push(T1)
        For i As Integer = 0 To NewStep.Length - 1
            Me.UnDoStepList.Push(NewStep(i))
        Next
        Dim T2 As StruStep
        With T2
            .Action = StepName.MulitEnd
            .Text = ActionName.ToString
            .NewText = Range
        End With
        Me.UnDoStepList.Push(T2)
        If IsRaiseEvent Then RaiseEvent UnReDoChanged()
    End Sub
    ''' <summary>
    ''' 清除撤销列表
    ''' </summary>
    ''' <remarks></remarks>
    Public Sub Clear()
        UnDoStepList.Clear()
        ReDoStepList.Clear()
    End Sub
    ''' <summary>
    ''' 打印撤销列表[测试用]
    ''' </summary>
    ''' <remarks></remarks>
    Public Sub DumpStep()
        Dim DebugFile As New StreamWriter(System.Windows.Forms.Application.StartupPath & "\DumpStepFile.txt", False, Encoding.Unicode)
        DebugFile.WriteLine("UndoList")
        For Each number As StruStep In UnDoStepList
            DebugFile.WriteLine(number.Action.ToString & " Pos:" & number.Pos.ToString)
            DebugFile.WriteLine("Text:" & number.Text)
            DebugFile.WriteLine("New Text:" & number.NewText)
        Next
        DebugFile.WriteLine("RedoList")
        For Each number As StruStep In ReDoStepList
            DebugFile.WriteLine(number.Action.ToString & " Pos:" & number.Pos.ToString)
            DebugFile.WriteLine("Text:" & number.Text)
            DebugFile.WriteLine("New Text:" & number.NewText)
        Next
        DebugFile.Close()
        System.Diagnostics.Process.Start(System.Windows.Forms.Application.StartupPath & "\DumpStepFile.txt")
    End Sub
    ''' <summary>
    ''' 操作步骤
    ''' </summary>
    ''' <remarks></remarks>
    Public Structure StruStep
        ''' <summary>
        ''' 步骤位置
        ''' </summary>
        ''' <remarks></remarks>
        Dim Pos As Point
        ''' <summary>
        ''' 步骤名称
        ''' </summary>
        ''' <remarks></remarks>
        Dim Action As StepName
        ''' <summary>
        ''' 步骤内容
        ''' </summary>
        ''' <remarks></remarks>
        Dim Text As String
        ''' <summary>
        ''' 替换操作后的文字
        ''' </summary>
        ''' <remarks>这个字段只有在替换操作时有效</remarks>
        Dim NewText As String
        ''' <summary>
        ''' 列操作长度
        ''' </summary>
        ''' <remarks></remarks>
        Dim ColumnLength As Integer
    End Structure
End Class